/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jpm.jroguefx.factories;

import com.jpm.jroguefx.Constants;
import com.jpm.jroguefx.utilities.PerfLogger;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.text.Font;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.CodeSource;
import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class GraphicsFactory extends ConcurrentHashMap<String, Image> implements Constants {

    private static final long serialVersionUID = 4895202259279060485L;
    private static GraphicsFactory instance;
    private final String fontsPath = graphicsPath + File.separator + "fonts";
    private final HashMap<FontId, Font> fontCache = new HashMap<>();
    private boolean loadFromJar = false;
    private URL location;
    private final Logger logger = Logger.getLogger(GraphicsFactory.class.getName());
    private final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());


    public static synchronized GraphicsFactory getInstance() {
        if (instance == null) {
            instance = new GraphicsFactory();
        }
        return instance;
    }

    private GraphicsFactory() {

        PerfLogger pl = new PerfLogger(this, "Graphics");
        final CodeSource src = GraphicsFactory.class.getProtectionDomain().getCodeSource();
        if (src != null) {
            location = src.getLocation();

            if (location.toString().toLowerCase().endsWith(jarExt)) {
                logger.log(Level.INFO, "Loading graphics from jar");
                loadFromJar = true;
                try {
                    final ZipInputStream zip = new ZipInputStream(location.openStream());
                    ZipEntry entry;


                    while ((entry = zip.getNextEntry()) != null) {

                        final ZipEntry finalEntry = entry;
                        executorService.submit(new Runnable() {

                            @Override
                            public void run() {
                                final String file = finalEntry.getName();

                                if (isImage(file)) {
                                    put(stripFilename(new File(file)), makeTransparent(new Image(file)));
                                }
                            }
                        });
                    }

                    executorService.shutdown();

                } catch (IOException e) {
                    logger.log(Level.SEVERE, null, e);
                }
            } else {
                logger.log(Level.INFO, "Loading graphics from file system");
                try {
                    final File dir = new File(location.toURI().toString().substring(5) + File.separator + graphicsPath);
                    imageFinder(dir);
                    executorService.shutdown();

                } catch (URISyntaxException e) {
                    logger.log(Level.SEVERE, null, e);
                }
            }
        } else {
            logger.log(Level.SEVERE, "Error loading graphics");
        }

        // wait for all of the executor threads to finish
        try {
            executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            logger.log(Level.SEVERE, null, e);
        }

        pl.report();
    }

    private void imageFinder(final File dir) {

        if (dir != null) {
            for (File file : dir.listFiles()) {
                if (file.isDirectory()) {
                    imageFinder(file.getAbsoluteFile());
                } else {
                    final File finalFile = file;

                    executorService.submit(new Runnable() {

                        @Override
                        public void run() {

                            if (isImage(finalFile)) {
                                put(stripFilename(finalFile), makeTransparent(new Image(filePrefix + finalFile.getPath())));
                            }
                        }
                    });
                }
            }
        } else {
            logger.log(Level.SEVERE, "Failed to load graphics");
        }
    }

    private boolean isImage(final String file) {
        return (file.toLowerCase().endsWith(pngExt));
    }

    private boolean isImage(final File file) {
        return (isImage(file.toString()));
    }

    private String stripFilename(final File file) {
        return file.getName().substring(0, file.getName().lastIndexOf(dot));
    }

    private Image makeTransparent(final Image baseImage) {

        final int imageWidth = (int) baseImage.getWidth();
        final int imageHeight = (int) baseImage.getHeight();
        final PixelReader reader = baseImage.getPixelReader();
        final WritableImage transparentImage = new WritableImage(reader, imageWidth, imageHeight);
        final PixelWriter writer = transparentImage.getPixelWriter();

        for (int x = 0; x < imageWidth; x++) {
            for (int y = 0; y < imageHeight; y++) {
                if ((reader.getArgb(x, y) & magentaMask) == magentaMask) {
                    writer.setArgb(x, y, 0x00000000);
                }
            }
        }
        return transparentImage;
    }

    public Font getFont(String fontName, double size) {

        FontId fontId = new FontId(fontName, size);
        Font font = fontCache.get(fontId);

        if (font != null) {
            System.out.println("Loaded " + fontName + " from cache");
            return font;
        }

        if (loadFromJar) {
            font = Font.loadFont(fontsPath + File.separator + fontName + ttfExt, size);
            fontCache.put(fontId, font);
            return font;
        } else {
            try {
                font = Font.loadFont(filePrefix + location.toURI().toString().substring(5) + File.separator + fontsPath + File.separator + fontName + ttfExt, size);
                fontCache.put(fontId, font);
                return font;

            } catch (URISyntaxException ex) {
                Logger.getLogger(GraphicsFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        return null;
    }

    private class FontId {

        private final String fontName;
        private final double size;

        private FontId(String fontName, double size) {
            this.fontName = fontName;
            this.size = size;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj.getClass() != FontId.class) {
                return false;
            }

            FontId fontId = (FontId) obj;
            return (fontId.fontName.equals(this.fontName) && fontId.size == this.size);
        }

        @Override
        public int hashCode() {
            int hash = 5;
            hash = 79 * hash + Objects.hashCode(this.fontName);
            hash = 79 * hash + (int) (Double.doubleToLongBits(this.size) ^ (Double.doubleToLongBits(this.size) >>> 32));
            return hash;
        }
    }


}
